/*
 * Anillo.cpp
 *
 *  Created on: Mar 15, 2010
 *      Author: jgrande
 */

#include "GestorAnillo.h"

namespace middleware {

namespace comunicaciones {

void GestorAnillo::enviarUnicast(PDUPuntoAPunto& pdu) {
	logger << LogType::LOG_DEBUG_0 <<"\tenviando mensaje unicast a destino " <<
			pdu.destino << logger.endl;
	qalc.send(&pdu, (long)pdu.destino);
}

void GestorAnillo::enviarMulticast(PDUPuntoAPunto& pdu) {
	logger << LogType::LOG_DEBUG_0 << "enviando mensaje multicast numero " <<
			pdu.secuencia << logger.endl;

	receptores.lock();
	ListaReceptores::Iterador it = receptores.iterar();
	while(it.hasNext()) {
		Direccion dest = it.next();
		logger << LogType::LOG_DEBUG_0 <<"\tenviando a destino " << dest << logger.endl;
		// dejo como dirección destino la multicast para que el que lo
		// recibe sepa si es un mensaje uni- o multicast
		// pdu.destino = *it;
		qalc.send(&pdu, (long)dest);
	}
	receptores.unlock();
}

Secuencia GestorAnillo::enviarMensajesPendientes(Secuencia n) {
	PDUPuntoAPunto pdu;
	logger << LogType::LOG_DEBUG_0 <<"revisando mensajes salientes" << logger.endl;
	qrng.recv(&pdu, 0, false, true);
	while(qrng) {
		if(pdu.destino==DIR_MULTICAST) {
			pdu.secuencia = n++;
			enviarMulticast(pdu);
		} else {
			enviarUnicast(pdu);
		}
		qrng.recv(&pdu, 0, false, true);
	}
	return n;
}

void GestorAnillo::servidor() {
    // socket utilizado para recibir la conexión entrante del
    // host precedente en el anillo
    TCPSocket sockserver;
    int port = ConfigSingleton::instance().get<int>("puertoAnillo");

    // configura el socket como pasivo con una cola de espera de 1 cliente
    if(!sockserver.listen(port, 1)) {
        logger << LogType::LOG_ERROR <<"error en listen()" << logger.err << logger.endl;
        exit(1);
    }

    // acepta una conexión entrante
    logger << LogType::LOG_DEBUG_0 <<"esperando conexion de antecesor" << logger.endl;
    if(!(sock = sockserver.accept())) {
        logger << LogType::LOG_ERROR <<"error en accept()" << logger.err << logger.endl;
        exit(1);
    }

    // cierra el socket usado para establecer la conexión
    sockserver.close();

    logger << LogType::LOG_DEBUG_0 <<"conexión establecida con el antecesor" << logger.endl;

    forever {
        // recibe el token
        if(!sock.recv((token_t*)token)) {
            logger << LogType::LOG_ERROR <<"error recibiendo token" << logger.err << logger.endl;
            break;
        } else {
            // levanta el semáforo para permitir que el proceso cliente
            // procese el token
            logger << LogType::LOG_DEBUG_0 <<"recibí un token con valor " << token->n << logger.endl;
            sem.signal();
        }
    }

    sock.close();

    exit(0);
}

void GestorAnillo::cliente() {
    TCPSocket sock;
    DireccionTCP dirTCPSucesor = ConfigSingleton::instance().getHost(sucesor);
    dirTCPSucesor.port = ConfigSingleton::instance().get<int>("puertoAnillo");

    // se conecta con el siguiente host del anillo
    // si el intento de conexión falla, reintenta cada 10 segundos
    // indefinidamente
    logger << LogType::LOG_DEBUG_0 << "conectando con sucesor (dir. mw: " << sucesor <<
    		", dir. tcp:" << dirTCPSucesor.host << ":" << dirTCPSucesor.port <<
    		")..." << logger.endl;
    while(!sock.connect(dirTCPSucesor.host, dirTCPSucesor.port)) {
        logger << LogType::LOG_ERROR << "error en connect()" << logger.err << logger.endl;
        logger << LogType::LOG_DEBUG_0 <<"reintentando en 10 segundos" << logger.endl;
        sleep(10);
    }

    logger << LogType::LOG_DEBUG_0 <<"conexión establecida con el sucesor" << logger.endl;

    if(primero==true) {
    	logger << LogType::LOG_DEBUG_0 <<"iniciando la circulación del anillo" << logger.endl;
        token->n=SECUENCIA_INICIAL;
        sock.send((token_t*)token);
    }

    forever {
        // espera el arribo del token
        sem.wait();

        // a partir de acá debería hacerse el procesamiento del token
        // en este caso, espera un segundo e incrementa el contador
        token->n = enviarMensajesPendientes(token->n);

        // agrego este sleep para poder seguir el programa
        // sleep(5);

        // reenvía el token
        if(!sock.send((token_t*)token)) {
            logger << LogType::LOG_ERROR <<"error enviando token" << logger.err << logger.endl;
            break;
        } else {
            logger << LogType::LOG_DEBUG_0 <<"envié un token con valor " << token->n << logger.endl;
        }
    }

    sock.close();

    exit(0);
}

bool GestorAnillo::inicializar() {
    // inicializa el semáforo
    if(!sem.init(0)) {
        logger << LogType::LOG_ERROR <<"error inicializando semáforo" << logger.err << logger.endl;
        return false;
    }

    // inicializa el área compartida de memoria
    if(!token.allocate()) {
    	logger << LogType::LOG_ERROR <<"error alocando token" << logger.err << logger.endl;
    	sem.destroy();
        return false;
    }

    return true;
}

bool GestorAnillo::run() {
	SharedPointer<bool> result;
	pid_t pid;

	result.allocate();

	if((pid = fork())==-1) {
		logger << LogType::LOG_ERROR << "error lanzando anillo" << logger.err << logger.endl;
		result.free();
		return false;
	} else if(pid==0) {
		logger.set_pname("Middleware/GestorAnillo/Lanzador");
		logger << LogType::LOG_DEBUG_0 <<"lanzando procesos del anillo" << logger.endl;
		*result = inicializar() && lanzarServidor() && lanzarCliente();
		exit(*result);
	} else {
		logger << LogType::LOG_DEBUG_0 << "esperando inicialización del anillo" << logger.endl;
		while(waitpid(pid, NULL, 0)!=pid);
		if(!*result) {
			logger << LogType::LOG_ERROR << "error inicializando anillo" << logger.endl;
			stop();
			result.free();
			return false;
		} else {
			logger << LogType::LOG_DEBUG_0 << "anillo inicializado" << logger.endl;
			result.free();
			return true;
		}
	}
}

bool GestorAnillo::lanzarServidor() {
    if((pidsvr=fork())==0) {
        logger.set_pname("Middleware/GestorAnillo/Servidor");
        //Utiles::registrar_signal(SIGINT, sigint_handler);
        servidor();
    } else if(pidsvr==-1) {
        logger << LogType::LOG_ERROR <<"error en fork() del servidor" << logger.err << logger.endl;
        return false;
    }

    return true;
}

bool GestorAnillo::lanzarCliente() {
    if((pidclt=fork())==0) {
        logger.set_pname("Middleware/GestorAnillo/Cliente");
        //Utiles::registrar_signal(SIGINT, sigint_handler);
        cliente();
    } else if(pidclt==-1) {
        logger << LogType::LOG_ERROR <<"error en fork() del cliente" << logger.err << logger.endl;
        return false;
    }

    return true;
}

/**
 * Proceso principal. Inicializa el semáforo y el área compartida de memoria.
 * Lanza los procesos cliente y servidor. Si uno de ellos muere, mata al otro y
 * luego termina.
 */
GestorAnillo::GestorAnillo(Direccion sucesor, bool primero):
	sucesor(sucesor),
	primero(primero),
	pidsvr(-1),
	pidclt(-1),
	qalc(MiddlewareClienteSingleton::instance().getAlc()),
	qrng(MiddlewareClienteSingleton::instance().getRng()),
	receptores(MiddlewareClienteSingleton::instance().getListaReceptores()) {
}

GestorAnillo::~GestorAnillo() {

}

void GestorAnillo::stop() {
	if(pidsvr!=-1) {
		logger << LogType::LOG_DEBUG_0 <<"deteniendo servidor de anillo" << logger.endl;
		kill(pidsvr, SIGINT);
		while(waitpid(pidsvr, NULL, 0)==-1 && errno==EINTR);
		pidsvr = -1;
		logger << LogType::LOG_DEBUG_0 <<"servidor de anillo detenido" << logger.endl;
	}

	if(pidclt!=-1) {
		logger << "deteniendo cliente de anillo" << logger.endl;
		kill(pidclt, SIGINT);
		while(waitpid(pidclt, NULL, 0)==-1 && errno==EINTR);
		pidclt = -1;
		logger << LogType::LOG_DEBUG_0 <<"cliente de anillo detenido" << logger.endl;
	}

	logger << LogType::LOG_DEBUG_0 <<"liberando recursos IPC" << logger.endl;
	if(sem) sem.destroy();
	if(token) token.free();
}

}

}
